
package com.trydofor.id.a9cq;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;

import javax.annotation.concurrent.ThreadSafe;

import com.trydofor.aloe.exception.Exceptions;
import com.trydofor.id.core.A9CQ;
import com.trydofor.id.core.ApiExceptions;

/**
 * an atomic(lock-free) fixed long a9cq.
 * 
 * @author Shi Rongjiu (www.trydofor.com)
 * @since 0.9
 */
@ThreadSafe
public final class FixedLongA9cq implements A9CQ<Long> {
    
    //
    private final long       startWith;
    private final long       endupWith;
    private final int        increment;
    private final boolean    isCyclic;
    
    private final long       entire;
    private final AtomicLong unused;
    
    /**
     * an atomic(lock-free) fixed long a9cq.
     * 
     * @param isCyclic cyclic generation or not.
     * @param startWith the first value.
     * @param endupWith the last value.
     * @param increment the increment.
     * 
     * @throws IllegalArgumentException if got bad increment direction.
     */
    public FixedLongA9cq(boolean isCyclic, long startWith, long endupWith, int increment) {
        Exceptions.checkIllegalArgument(increment == 0, "increment[%s] NUST NOT equal 0", increment);
        Exceptions.checkIllegalArgument((endupWith - startWith) * increment < 0,
                "bad increment[%s] from %s to %s", increment, startWith, endupWith);
        
        this.startWith = startWith;
        this.increment = increment;
        this.endupWith = endupWith;
        this.isCyclic = isCyclic;
        //
        this.entire = (endupWith - startWith) / increment + 1; // MUST >0
        this.unused = new AtomicLong(entire);
    }
    
    /**
     * get the first value.
     * 
     * @return the first value.
     */
    public long getStartWith() {
        return startWith;
    }
    
    /**
     * get the last value.
     * 
     * @return the last value.
     */
    public long getEndupWith() {
        return endupWith;
    }
    
    /**
     * get the interval between consecutive values.
     * 
     * @return the interval between consecutive values of the sequence.
     */
    public int getIncrement() {
        return increment;
    }
    
    /**
     * whether cyclically generate sequence.
     * 
     * @return true if cyclic.
     */
    public boolean isCyclic() {
        return isCyclic;
    }
    
    /**
     * {@inheritDoc}
     */
    public boolean isAtomic() {
        return true;
    }
    
    /**
     * <pre>
     * return the count ,if cyclic is:
     * - true  - entire item.
     * - false - unused item.
     * </pre>
     * 
     * {@inheritDoc}
     */
    public long remains() {
        if (isCyclic) return entire;
        
        final long rem = currentRemain();
        return rem < 0 ? 0 : rem;
    }
    
    /**
     * {@inheritDoc}
     */
    public Long current() {
        return calculateValue(currentRemain() + 1);
    }
    
    /**
     * {@inheritDoc}
     */
    public Long next() {
        Long r = null;
        try {
            r = next(0, null);
        }
        catch (InterruptedException e) {
            ApiExceptions.throwFakeException(e);
        }
        catch (TimeoutException e) {
            ApiExceptions.throwFakeException(e);
        }
        return r;
    }
    
    /**
     * {@inheritDoc}
     */
    public Long next(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
        
        final long startedNano = System.nanoTime();
        final Thread curThread = Thread.currentThread();
        final boolean needTime = (timeout > 0 && unit != null);
        
        final long nxt;
        final long cur = unused.getAndDecrement(); // atomic
        if (cur <= 0) {
            if (isCyclic) {
                if (cur == 0) { // reset
                    unused.set(entire - 1); // atomic
                    nxt = entire;
                }
                else {
                    // waiting for reseting
                    while (true) {
                        long un = unused.get();
                        if (needTime) { // check
                            ApiExceptions.checkThreadInterrupted(curThread);
                            ApiExceptions.checkTimeoutNano(startedNano, timeout, unit);
                        }
                        if (un == 0) { // reset again
                            unused.set(entire - 1); // atomic
                            nxt = entire;
                            break;
                        }
                        if (un > 0) { // reset done
                            nxt = unused.getAndDecrement(); // atomic
                            break;
                        }
                    }
                }
            }
            else { // empty
                nxt = -1;
                unused.set(nxt); // atomic
            }
        }
        else {
            nxt = cur;
        }
        
        // post-check
        if (needTime) {
            ApiExceptions.checkThreadInterrupted(curThread);
            ApiExceptions.checkTimeoutNano(startedNano, timeout, unit);
        }
        
        return calculateValue(nxt);
    }
    
    /**
     * {@inheritDoc}
     */
    public void reset() {
        unused.set(entire);
    }
    
    /**
     * {@inheritDoc}
     */
    public String toString() {
        final long rem = currentRemain();
        StringBuilder sb = new StringBuilder();
        sb.append("isCyclic=");
        sb.append(isCyclic);
        sb.append(",startwith=");
        sb.append(startWith);
        sb.append(",endupwith=");
        sb.append(endupWith);
        sb.append(",increment=");
        sb.append(increment);
        sb.append(",remains=");
        sb.append((isCyclic ? entire : rem));
        sb.append(",current=");
        sb.append(calculateValue(rem + 1));
        return sb.toString();
    }
    
    //-----------------------------------------------------------------------------
    
    private long currentRemain() {
        final long lft = unused.get();
        if (lft >= entire) { // no current before the first call.
            return entire;
        }
        else if (lft < 0) { // empty after the last call.
            if (isCyclic) {
                while (true) { // waiting for reset()
                    long wait = unused.get();
                    if (wait >= 0) return wait;
                }
            }
            else {
                return -1;
            }
        }
        return lft;
    }
    
    private Long calculateValue(long unused) {
        if (unused > entire || unused <= 0) return null;
        return Long.valueOf(startWith + (entire - unused) * increment);
    }
}
